﻿#region License
/*
Copyright (c) 2009, Open University of the Netherlands
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided 
that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the 
  following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
  the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of Open University of the Netherlands nor the names of its contributors may be 
  used to endorse or promote products derived from this software without specific prior written 
  permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
*/
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using CSScriptLibrary;
using System.Windows.Forms;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;

namespace EwbDesigner.NET.Classes
{
    /// <summary>
    /// Helper Class to Wrap the CS-Script Engine.
    /// 
    /// TODO are some more properties like extra assemblies.
    /// </summary>
    public class ScriptHelper : IDisposable
    {
        public ScriptHelper()
        {
            Helpers = new Dictionary<string, AsmHelper>();

            Console.WriteLine("[Resources]");
            foreach (String rn in Assembly.GetExecutingAssembly().GetManifestResourceNames())
            {
                Console.WriteLine(rn);
            }
            Console.WriteLine();
        }

        /// <summary>
        /// Filename that can be relative to the current directory.
        /// </summary>
        public String Filename
        {
            get
            {
                return fFileName;
            }
            set
            {
                fFileName = Path.GetFullPath(value);
                if (File.Exists(Filename))
                {
                    Code = File.ReadAllText(fFileName);
                }
            }
        }
        private String fFileName = null;

        /// <summary>
        /// Code to be executed. This property is either set directly or 
        /// indirectly by assigning values to either the Filename or Resource property.
        /// </summary>
        public String Code
        {
            get;
            set;
        }

        /// <summary>
        /// Enable or disable Debugging.
        /// </summary>
        public Boolean DebugBuild
        {
            get;
            set;
        }

        /// <summary>
        /// Resource name like : 'EwbDesigner.NET.BlackboardExport.cs'.
        /// </summary>
        public String Resource
        {
            get
            {
                return fResource;
            }
            set
            {
                fResource = value;

                /*
String apppath = Application.ExecutablePath.Replace(".vshost", "");
Assembly asm = Assembly.LoadFrom(apppath);
if (asm != null && asm.GetManifestResourceInfo(Resource) != null)
{
    return new StreamReader(asm.GetManifestResourceStream(Resource)).ReadToEnd();
}
return null;
                 */
                //Read Script from Executable
                if (Assembly.GetExecutingAssembly().GetManifestResourceInfo(fResource) != null)
                {
                    Code = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(fResource)).ReadToEnd();
                }
            }
        }
        private String fResource = null;

        public String[] Additional
        {
            get { return fAdditional; }
            set { fAdditional = value; }
        }
        private String[] fAdditional = new String[] { };

        public readonly String[] Assemblies = new String[] { "System", "System.IO", "System.Text", "System.Xml", "System.Reflection" };

        /// <summary>
        /// Collection of Keys and Pre-Compiled Scripts that can be Invoked.
        /// </summary>
        private Dictionary<String, AsmHelper> Helpers
        {
            get;
            set;
        }
        /// <summary>
        /// Resets all Properties excluding Pre-Compiled Scripts.
        /// </summary>
        public void Reset()
        {
            Code = null;
            DebugBuild = false;

            fFileName = null;
            fResource = null;
        }

        /// <summary>
        /// Resets Pre-Compiled Scripts.
        /// </summary>
        public void ResetHelpers()
        {
            Helpers.Clear();
        }

        /// <summary>
        /// Execute a script without parameters and unloads it. 
        /// </summary>
        public string Execute()
        {
            return Execute(new Object[] { }, true);
        }

        /// <summary>
        /// Execute a script without parameters. 
        /// </summary>
        /// <param name="unload">Unload script/dll after execution</param>
        public string Execute(Boolean unload)
        {
            return Execute(new Object[] { }, unload);
        }

        /// <summary>
        /// Execute a script and unloads it. 
        /// </summary>
        /// <param name="parms">Parameters for the script</param>
        public string Execute(Object[] parms)
        {
            return Execute(parms, true);
        }

        /// <summary>
        /// Execute a script. 
        /// </summary>
        /// <param name="parms">Parameters for the script</param>
        /// <param name="unload">Unload script/dll after execution</param>
        public String Execute(Object[] parms, Boolean unload)
        {
            if (Code != null)
            {
                switch (unload)
                {
                    case true:
                        String s = CSScript.CompileCode(Code, null, this.DebugBuild, ExpandAssemblies());
                        using (AsmHelper helper = new AsmHelper(s, "cs", true))
                        {
                            //Security problems here (due to deleting an assembly?).
                            return (String)helper.Invoke("*.Execute", parms);
                        }

                    case false:
                        Assembly a = CSScript.LoadCode(Code, null, this.DebugBuild, ExpandAssemblies());
                        using (AsmHelper helper = new AsmHelper(a))
                        {
                            String Result = (String)helper.Invoke("*.Execute", parms);
                            //Problems unloading due to running threads.
                            AppDomain.Unload(helper.ScriptExecutionDomain);

                            return Result;
                        }
                }
            }

            throw new ScriptHelperException("No Script code found!");
        }

        /// <summary>
        /// Pre-Compiles a Script. 
        /// </summary>
        /// <param name="parms">The key identifying the script</param>
        public void Compile(String Key, String Code)
        {
            if (Code != null)
            {
                this.Code = Code;
                AsmHelper asm = new AsmHelper(CSScript.LoadCode(this.Code, null, this.DebugBuild, ExpandAssemblies()));
                Helpers.Add(Key, asm);
            }
            else
            {
                throw new ScriptHelperException("No Script code found!");
            }
        }

        /// <summary>
        /// Executes a pre-Compiled Script. 
        /// </summary>
        /// <param name="Key">The key identifying the script</param>
        /// <param name="parms">Parameters for the script</param>
        /// <returns>Script output</returns>
        public String Invoke(String Key, Object[] parms)
        {
            return (String)Helpers[Key].Invoke("*.Execute", parms);
        }

        /// <summary>
        /// Executes a pre-Compiled Script. 
        /// </summary>
        /// <param name="Key">The key identifying the script</param>
        /// <returns>Script output</returns>
        public String Invoke(String Key)
        {
            return (String)Helpers[Key].Invoke("*.Execute", new Object[] { });
        }

        /// <summary>
        /// Removes a pre-Compiled Script. 
        /// </summary>
        /// <param name="parms">The key identifying the script</param>
        public void Remove(String Key)
        {
            if (Helpers.ContainsKey(Key))
            {
                Helpers[Key].Dispose();
                Helpers.Remove(Key);
            }
        }

        private String[] ExpandAssemblies()
        {
            AssemblyName csa = AssemblyName.GetAssemblyName("CSScriptLibrary.dll");

            Console.WriteLine("[CSScript]");
            Debug.Print("Version={0}", csa.Version);                              //2.5.0.0

            string[] assemblies = new String[0];

            //Add Standard Assemblies
            for (Int32 i = 0; i < Assemblies.Length; i++)
            {
                Array.Resize(ref assemblies, assemblies.Length + 1);
                assemblies[assemblies.Length - 1] = String.Format(@"{0}{1}.dll", System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(), Assemblies[i]);
            }

            //Add Additional Assemblies
            for (Int32 i = 0; i < Additional.Length; i++)
            {
                Array.Resize(ref assemblies, assemblies.Length + 1);
                assemblies[assemblies.Length - 1] = String.Format(@"{0}\{1}.dll", Application.StartupPath, Additional[i]);
            }

            //Add Aplication (so loading script resources works).
            Array.Resize(ref assemblies, assemblies.Length + 1);
            assemblies[assemblies.Length - 1] = Assembly.GetEntryAssembly().Location;

            foreach (String asm in assemblies)
            {
                Debug.Print("Using={0}", asm);
            }
            Debug.Print("");

            return assemblies;
        }

        #region IDisposable Members

        void IDisposable.Dispose()
        {
            Reset();
            ResetHelpers();
        }

        #endregion

        public class ScriptHelperException : System.ArgumentNullException
        {
            public ScriptHelperException(string message) :
                base(message)
            {
                //Nothing...
            }
        }
    }
}
